#pragma once

#include <iostream>
#include <functional>
#include <unordered_map>
#include <string>
#include <cassert>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include "Log.hpp"
#include "Error.hpp"
#include "sock_tcp.hpp"
#include "Epoller.hpp"
#include "Connection.hpp"
#include "Ulit.hpp"
#include "Protocol.hpp"
#include "pthreadPool.hpp"
#include "Task.hpp"

class EpollServer
{
private:
    // typedef
    using func_t = std::function<protocol_ns::Response(protocol_ns::Request)>;
    // const
    static const int N = 4080;
    static const int defaultPort = 8888;
    static const int defaultFd = -1;

public:
    EpollServer(func_t func = nullptr, uint16_t port = defaultPort)
        : _port(port), _func(func)
    {
    }
    ~EpollServer()
    {
        _listenSock.Close();
    }

    void init()
    {
        logMessage(Debug, "EpollServer init");

        // 1. 创建listensock
        _listenSock.Socket();
        _listenSock.Bind(_port);
        _listenSock.Listen();

        // 2. 创建epoll
        _epoller.create();

        // 3. 对listenSock 进行添加管理
        addConnection(_listenSock.Fd(), EPOLLIN | EPOLLET);

        logMessage(Debug, "EpollServer init success");
    }

    void dispatcher()
    {
        logMessage(Debug, "EpollServer dispatcher");
        while (true)
        {
            loopOnce();
        }
    }

private:
    void addConnection(int fd, uint32_t events, const std::string ip = "127.0.0.1", const uint16_t port = defaultPort)
    {
        // 1. ET模式下，必须是非阻塞的
        Ulit::setNonBlock(fd);

        // 2. 将fd添加到connections_中进行管理
        // 2.1 创建Connection对象
        Connection *conn = new Connection(fd, events, ip, port);
        // 2.2 注册回调函数
        if (fd == _listenSock.Fd())
        {
            conn->registerCallBack(std::bind(&EpollServer::accepter, this, std::placeholders::_1),
                                   nullptr,
                                   nullptr);
        }
        else
        {
            conn->registerCallBack(std::bind(&EpollServer::recvConn, this, std::placeholders::_1),
                                   std::bind(&EpollServer::sendConn, this, std::placeholders::_1),
                                   std::bind(&EpollServer::exceptionConn, this, std::placeholders::_1));
        }
        // 2.3 添加到map中
        _connMap.insert(std::make_pair(fd, conn));

        // 3. 将fd添加到epoll中
        bool flag = _epoller.addEvent(fd, events);
        if (!flag)
        {
            close(fd);
            logMessage(Warning, "client %s:%d , epoll add error:%d", ip.c_str(), port, fd);
        }
        else
        {
            logMessage(Info, "client %s:%d , epoll add success:%d", ip.c_str(), port, fd);
        }
    }

    void loopOnce()
    {
        logMessage(Debug, "EpollServer loopOnce ...");

        // 1. 调用epoll_wait
        int n = _epoller.wait(_events, N, -1);

        // 2. 处理就绪事件
        handlerEvent(n);
    }

    void handlerEvent(int n)
    {
        if (n <= 0)
            return;
        logMessage(Debug, "handlerEvent ...");
        // 2.1 遍历fd_array，找到就绪的fd
        for (int i = 0; i < n; ++i)
        {
            // a. 提取相关信息
            int fd = _events[i].data.fd;
            uint32_t events = _events[i].events;
            logMessage(Debug, "当前正在处理%d上的%s", fd, (events & EPOLLIN) ? "EPOLLIN" : "OTHER");

            // 我们将所有的异常情况，最后全部转化成为recv，send的异常！
            if ((events & EPOLLERR) || (events & EPOLLHUP))
            {
                events |= (EPOLLIN | EPOLLOUT);
            }

            // b. 根据fd在map中查找对应的Connection对象
            bool flag = isFdInConnMap(fd);

            // b.1 处理读事件
            if (flag && (events & EPOLLIN))
            {
                // c. 调用Connection对象的回调函数
                Connection *conn = _connMap[fd];

                // 接入线程池组件
                Task task(conn->_readCallBack, conn);
                ThreadPool<Task>::getInstance()->pushTask(task);
                // conn->_readCallBack(conn);
            }
            // b.2 处理写事件
            if (flag && (events & EPOLLOUT))
            {
                // c. 调用Connection对象的回调函数
                Connection *conn = _connMap[fd];

                // 接入线程池组件
                Task task(conn->_writeCallBack, conn);
                ThreadPool<Task>::getInstance()->pushTask(task);
                // conn->_writeCallBack(conn);
            }
        }
    }

    bool isFdInConnMap(int fd)
    {
        auto it = _connMap.find(fd);
        if (it == _connMap.end())
        {
            logMessage(Warning, "fd %d not in map", fd);
            return false;
        }
        else
        {
            logMessage(Info, "fd %d in map", fd);
            return true;
        }
    }

    // listenSock读事件的回调函数
    void accepter(Connection *conn)
    {
        // 走到这里，说明listensock就绪了！调用accept是非阻塞的
        logMessage(Debug, "EpollServer accepter ...");
        // ET模式下 -- 循环读取
        do
        {
            // 1. 调用accept
            std::string clientIP;
            uint16_t clientPort;
            int newsockfd = _listenSock.Accept(&clientIP, &clientPort);
            if (newsockfd < 0)
            {
                logMessage(Warning, "accept error");
                break;
            }
            else
            {
                logMessage(Info, "client %s:%d connect success", clientIP.c_str(), clientPort);
            }

            // 2. 将newsockfd进行管理
            addConnection(newsockfd, EPOLLIN | EPOLLET, clientIP, clientPort);
        } while (conn->_events & EPOLLET);

        logMessage(Debug, "EpollServer accepter success");
    }

    // 服务sockfd的读事件的回调函数
    void recvConn(Connection *conn)
    {
        int newsockfd = conn->_fd;
        //  在ET的工作模式下，我们需要轮询的非阻塞读取数据（已经设置），直到读上来的数据小于预期的值
        do
        {
            char buf[1024] = {0};
            ssize_t n = recv(newsockfd, buf, sizeof(buf) - 1, 0);
            if (n > 0)
            {
                buf[n] = '\0';
                logMessage(Info, "recv data : %s", buf);
                // 保存数据到输入缓冲区中
                conn->_inBuffer += buf;
            }
            else if (n == 0)
            {
                logMessage(Info, "peer shutdown");
                // 异常处理 -- 释放相关资源
                conn->_exceptionCallBack(conn);
                break;
            }
            else
            {
                // 通知一次，读取完毕
                if (errno == EAGAIN || errno == EWOULDBLOCK)
                {
                    logMessage(Info, "recv data done");
                    break;
                }
                else // 真正出现异常
                {
                    logMessage(Error, "recv error : %s", strerror(errno));
                    // 异常处理 -- 释放相关资源
                    conn->_exceptionCallBack(conn);
                    break;
                }
            }
        } while (conn->_events & EPOLLET);

        // 读取完毕，触发一次写事件
        if (isFdInConnMap(newsockfd)) // 防止在读取的过程中，连接被关闭
        {
            requestHandler(conn);
            if (conn->_outBuffer.size() > 0)
                _epoller.modEvent(newsockfd, EPOLLIN | EPOLLOUT | EPOLLET);
            else
                _epoller.modEvent(newsockfd, EPOLLIN | EPOLLET);
        }
    }

    void requestHandler(Connection *conn)
    {
        int sockfd = conn->_fd;

        // 1. 从输入缓冲区中提取一条完整的数据
        std::string package;
        int len = 0;
        while (!(len = protocol_ns::ReadStrPackage(sockfd, conn->_inBuffer, &package)))
            ; // 只有存在一条完整的报文，才会跳出循环
        // 2. 处理报头
        // 2.1 提取报头
        std::string header = protocol_ns::RemoveHeads(package, len);
        // 2.2 反序列化
        protocol_ns::Request req;
        req.Deserialization(header);
        // 3. 处理有效载荷
        protocol_ns::Response rsp = _func(req);
        // 4. 序列化
        std::string sending_string = rsp.Serialization();
        // 5. 添加报头
        std::string sending_package = protocol_ns::AddHeads(sending_string);
        // 6. 将数据添加到输出缓冲区中
        conn->_outBuffer += sending_package;
    }

    // 服务sockfd的写事件的回调函数
    void sendConn(Connection *conn)
    {
        int newsockfd = conn->_fd;
        logMessage(Debug, "EpollServer sendConn:%d ...", newsockfd);
        //  在ET的工作模式下，我们需要轮询的非阻塞写数据（已经设置），直到写上去的数据小于预期的值
        // 关心写事件是需要按需开启的，因为和读事件相反，写是缓冲区没有满的话就会一直通知你
        do
        {
            ssize_t n = send(newsockfd, conn->_outBuffer.c_str(), conn->_outBuffer.size(), 0);
            if (n > 0)
            {
                logMessage(Info, "send data : %s", conn->_outBuffer.c_str());
                // 从输出缓冲区中删除已经发送的数据
                conn->_outBuffer.erase(0, n);

                if (conn->_outBuffer.size() == 0)
                {
                    break;
                }
            }
            else
            {
                if (errno == EAGAIN || errno == EWOULDBLOCK)
                {
                    logMessage(Info, "send data done");
                    break;
                }
                else
                {
                    logMessage(Warning, "send error : %s", strerror(errno));
                    // 异常处理 -- 释放相关资源
                    conn->_exceptionCallBack(conn);
                    break;
                }
            }
        } while (conn->_events & EPOLLET);

        // 自己判断是否需要继续关注写事件
        if (conn->_outBuffer.size() == 0)
        {
            // 不需要关注写事件
            _epoller.modEvent(newsockfd, EPOLLIN | EPOLLET);
        }
        else
        {
            // 需要关注写事件
            _epoller.modEvent(newsockfd, EPOLLIN | EPOLLOUT | EPOLLET);
        }
    }

    // 异常事件的回调函数
    void exceptionConn(Connection *conn)
    {
        logMessage(Debug, "EpollServer exceptionConn ...");

        // 1. 提取相关信息
        int newsockfd = conn->_fd;
        // 2. 从epoll中删除
        _epoller.delEvent(newsockfd);
        // 3. 从map中删除
        _connMap.erase(newsockfd);
        // 4. 关闭文件描述符
        close(newsockfd);
        // 5. 释放资源
        delete conn;

        logMessage(Debug, "EpollServer exceptionConn success: %d", newsockfd);
    }

private:
    uint16_t _port;
    SockTCP _listenSock;
    func_t _func;
    struct epoll_event _events[N];
    Epoller _epoller;
    std::unordered_map<int, Connection *> _connMap;
};
